/*
 * jAutomation: Portable input Automation 
 * Copyright (C) 2012 Dominik Hensler.  
 * http://code.google.com/p/jautomation
 * 
 * This Program uses JNativeHook by Alexander Barker:
 * JNativeHook: Global keyboard and mouse hooking for Java.
 * Copyright (C) 2006-2012 Alexander Barker.  All Rights Received.
 * http://code.google.com/p/jnativehook/
 *
 * 
 * This file is part of JAutomation.

 * JAutomation is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * JAutomation is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with JAutomation.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jautomation.core;

import java.awt.AWTException;
import java.awt.MouseInfo;
import java.awt.Robot;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jautomation.commands.*;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;

public class Automation implements NativeKeyListener {

    private static Map<String, ICommand> syntax = new HashMap<>();
    private Robot processor;
    private PchangeThread detector;
    private static Properties configFile;
    private static boolean ktest;
    private volatile MainContext context = new MainContext();
    public static final String VERSION = "jAutomation Version 0.95.00\n";
    private static String CONFIG_PATH = "config.csv";    

    public Automation() {
        configFile = new java.util.Properties();
        try {
            this.processor = new Robot();
        } catch (AWTException ex) {
            Logger.getLogger(Automation.class.getName()).log(Level.SEVERE, null, ex);
            System.exit(-1);
        }
        Automation.ktest = false;
        this.detector = new PchangeThread(this);
        createCommands();
    }

    private void optionsParser(String[] args) {
        if (args.length > 0) {
            if (args[0].matches("-t")) {
                Automation.ktest = true;
            } else if (args[0].matches("--version")) {
                System.out.println(VERSION);
                showLegalNotice();
                System.exit(0);
            } else if (args[0].matches("-c")) {
                CONFIG_PATH = args[1];
                this.loadConfig(false);
            } else if (args[0].matches("-p")) {
                this.loadConfig(true);
            } else {
                showUsageMessage();
                System.exit(0);
            }
        } else {
            showUsageMessage();
            System.exit(0);
        }
    }

    public static void main(String[] args) {
        try {
            GlobalScreen.registerNativeHook();
        } catch (NativeHookException ex) {
            Logger.getLogger(Automation.class.getName()).log(Level.SEVERE, null, ex);
            System.err.println("There was a problem registering the native hook.");
            System.err.println(ex.getMessage());
            System.exit(-1);
        }
        Automation automation = new Automation();
        GlobalScreen.getInstance().addNativeKeyListener(automation);
        automation.optionsParser(args);
        automation.detector.start();
    }

    private void loadConfig(boolean fromPackage) {
        if (fromPackage == true) {
            InputStream in = getClass().getResourceAsStream(CONFIG_PATH);
            try {
                configFile.load(in);
            } catch (IOException ex) {
                System.out.println("unable to load file from package");
                Logger.getLogger(Automation.class.getName()).log(Level.SEVERE, null, ex);
                System.exit(-1);
            }
        } else {
            try {
                FileInputStream is = new FileInputStream(CONFIG_PATH);
                configFile.load(is);
            } catch (IOException ex) {
                System.out.println("Unable to access file " + CONFIG_PATH);
                System.exit(-1);
            }
        }
    }

    @Override
    public void nativeKeyPressed(NativeKeyEvent e) {
    }

    @Override
    public void nativeKeyReleased(NativeKeyEvent e) {
        if (Automation.ktest == true) {
            System.out.println("Key Released: " + e.getKeyCode() + "  " + e.isActionKey());
            System.out.println("Mouse at ("
                    + MouseInfo.getPointerInfo().getLocation().x + ", "
                    + MouseInfo.getPointerInfo().getLocation().y + ")");
        } else {
            prepareCommand(e);
        }
    }

    @Override
    public void nativeKeyTyped(NativeKeyEvent e) {
    }

    private void createCommands() {
        KeyReceiver keyReceiver = new KeyReceiver(this.processor);
        MouseReceiver mouseReceiver = new MouseReceiver(this.processor);
        ControlReceiver controlReceiver = new ControlReceiver(this.processor,
                this.detector);
        Automation.syntax.put("SHIFT", new ShiftHoldCommand(keyReceiver));
        Automation.syntax.put("srelease", new ShiftReleaseCommand(keyReceiver));
        Automation.syntax.put("key", new KeyCommand(keyReceiver));
        Automation.syntax.put("move", new MouseMoveCommand(mouseReceiver));
        Automation.syntax.put("lpress", new MousePressLeftCommand(mouseReceiver));
        Automation.syntax.put("rpress", new MousePressRightCommand(mouseReceiver));
        Automation.syntax.put("lrelease", new MouseReleaseLeftCommand(mouseReceiver));
        Automation.syntax.put("rrelease", new MouseReleaseRightCommand(mouseReceiver));
        Automation.syntax.put("delay", new DelayCommand(controlReceiver));
        Automation.syntax.put("pdetectOn", new PixelDetectOnCommand(controlReceiver));
        Automation.syntax.put("pdetectOff", new PixelDetectOffCommand(controlReceiver));
        Automation.syntax.put("pmonitorOn", new PixelMonitorOnCommand(controlReceiver));
        Automation.syntax.put("pmonitorOff", new PixelMonitorOffCommand(controlReceiver));
        Automation.syntax.put("pselect", new PixelSelectCommand(controlReceiver));
        Automation.syntax.put("find",  new PixelFindCommand(controlReceiver));
        Automation.syntax.put("exit", new ExitCommand(controlReceiver));
    }

    private void showUsageMessage() {
        System.out.println("Unrecognized command line option.");
        System.out.println("Usage:  jAutomation [option]");
        System.out.println("\t-t test Keycodes");
        System.out.println("\t-c <path> load configuration file");
        System.out.println("\t-p load configuration from package");
        System.out.println("\t--version display version information");
        System.exit(-1);
    }

    private void showLegalNotice() {
        try {
            String line = "";
            InputStream msgRead = getClass().getResourceAsStream("copymsg");
            BufferedReader br = new BufferedReader(new InputStreamReader(msgRead));
            while (null != (line = br.readLine())) {
                System.out.println(line);
            }
        } catch (IOException ioex) {
            Logger.getLogger(Automation.class.getName()).log(Level.SEVERE, null, ioex);            
            System.exit(-1);
        }
    }

    private void prepareCommand(NativeKeyEvent e) {
        String inputBuffer = Integer.toString(e.getKeyCode());
        executeCommand(inputBuffer);
    }
    
    private void prepareCommand(TimedEvent e){
        String inputBuffer=e.getType();
        executeCommand(inputBuffer);
    }
        
    private void executeCommand(String inputBuffer){
        CommandInvoker keyInvoker = new CommandInvoker();
        if (Automation.configFile.containsKey(inputBuffer) && Automation.ktest == false) {
            context.setCommandArray(configFile.getProperty(inputBuffer).split(","));
            for (int i = 0; i < context.getCommandArray().length; i++) {
                context.setPosition(i);
                if (Automation.syntax.containsKey(context.getCommandArray()[i])) {
                    keyInvoker.prepare(Automation.syntax.get(context.getCommandArray()[i]));
                    keyInvoker.invoke(context);
                }
            }
        }
    }        

    public void setEvent(TimedEvent e) {
        synchronized (e) {
            prepareCommand(e);
        }
    }
}